/*
 * TCSS 422 - Spring quarter 2011
 * Team: 	Googlers
 * Members: Deepa Sahni, Krisnil Gounder, Michael Schweiger
 * Date: 	April 24, 2011
 */
package web;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

import ui.Reporter;

public class Gatherer2 implements Runnable {

	//**********************************************
	//	Constants for accessing the header map.
	//**********************************************
	public static final String PAGES_RETRIEVED = "Pages";
	public static final String TOTAL_WORDS_SCANNED = "Words";
	public static final String TOTAL_LINKS_SCANNED = "Links";
	public static final String TIME_TAKEN_FOR_PAGE = "Time";
	
	//**********************************************
	//	Instance fields
	//**********************************************
	
	/**
	 * The map of terms to counts (for each term).
	 */
	private final Map<String, Integer> my_term_counts = new HashMap<String, Integer>();

	/**
	 * The map of "Pages", "Words", and  "Links" to the respective counts.
	 */
	private final Map<String, Long> my_header_counts = new HashMap<String, Long>();
	
	/**
	 * The queue of pages to process.
	 */
	private final List<String> my_page_queue = new LinkedList<String>();
	
	/**
	 * The Reporter object to send results to.
	 */
	private final Reporter my_reporter;
	
	/**
	 * Determines whether the gatherer is running when in multi-threaded mode.
	 */
	private boolean gatherer_running = false;
	
	/**
	 * Initializes the Gatherer.
	 * @param the_terms The terms to search for.
	 * @param the_reporter The reporter to send results to.
	 */
	public Gatherer2(final Set<String> the_terms, final Reporter the_reporter) {
		if (the_terms == null || the_reporter == null) {
			throw new IllegalArgumentException("The arguments cannot be null!");
		}
		my_reporter = the_reporter;
		
		//Initialize the term counts to 0.
		for (String term : the_terms) {
			my_term_counts.put(term, 0);
		}
		//Initialize the header counts to 0.
		my_header_counts.put(Gatherer2.PAGES_RETRIEVED, 0L);
		my_header_counts.put(Gatherer2.TOTAL_WORDS_SCANNED, 0L);
		my_header_counts.put(Gatherer2.TOTAL_LINKS_SCANNED, 0L);
		my_header_counts.put(Gatherer2.TIME_TAKEN_FOR_PAGE, 0L);
	}
	
	/**
	 * Synchronized add method to enforce thread-safety.
	 * @param the_page The page to add.
	 */
	public synchronized void addPage(final String the_page) {
		my_page_queue.add(the_page);
		synchronized (this) {
			try {
				this.notify();
			} catch (IllegalMonitorStateException e) {
				
			}
		}
	}

	/**
	 * Starts the gatherer running.  Useful in multi-threaded environments.
	 * If this method is called in a single-threaded environment, it will result in an
	 * infinite loop.  If calling from a single-threaded environment, simply call
	 * process_next_page() instead.
	 */
	public void run() {
		gatherer_running = true;
		while (gatherer_running) {
			synchronized(this) {
				if (my_page_queue.isEmpty()) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						
					}
				}
			}
			process_next_page();
		}
	}
	
	/**
	 * Stops the gatherer, allows the gatherer to terminate in multi-threaded environments.
	 */
	public void stop() {
		gatherer_running = false;
		synchronized(this) {
			try {
				this.notify();
			} catch (IllegalMonitorStateException e) {
				
			}
		}
	}
	
	/**
	 * Analyzes the next page in the queue, if at least one exists.  Passes the results to
	 * the Reporter when finished.
	 */
	public final void process_next_page() {
		if (!my_page_queue.isEmpty()) {
			String page = my_page_queue.remove(0);
			Scanner counter = new Scanner(page);
			
			if (!counter.hasNext()) {
				throw new IllegalStateException("Page isn't formatted properly");
			}
			String URL = counter.next();

			if (!counter.hasNextFloat()) {
				throw new IllegalStateException("Page isn't formatted properly");
			}
			int links_in_page = counter.nextInt();

			if (!counter.hasNextFloat()) {
				throw new IllegalStateException("Page isn't formatted properly");
			}
			long time_taken = counter.nextLong();
			int word_count = 0;
			
			while (counter.hasNext()) {
				String word = counter.next();
				word = word.toLowerCase();
				if (my_term_counts.containsKey(word)) {
					my_term_counts.put(word, my_term_counts.get(word) + 1);
				}
				word_count++;
			}
			my_header_counts.put(Gatherer2.PAGES_RETRIEVED, 
					my_header_counts.get(Gatherer2.PAGES_RETRIEVED) + 1);
			my_header_counts.put(Gatherer2.TOTAL_LINKS_SCANNED, 
					my_header_counts.get(Gatherer2.TOTAL_LINKS_SCANNED) + links_in_page);
			my_header_counts.put(Gatherer2.TOTAL_WORDS_SCANNED, 
					my_header_counts.get(Gatherer2.TOTAL_WORDS_SCANNED) + word_count);
			my_header_counts.put(Gatherer2.TIME_TAKEN_FOR_PAGE, time_taken);
			List<Object> data = new ArrayList<Object>(3);
			data.add(URL);
			data.add(my_header_counts);
			data.add(my_term_counts);
			my_reporter.add(data);
		}
	}
}
